﻿using System;
using System.Web;
using System.Net;
using System.IO;
using System.Text.RegularExpressions;
using System.Text;
using System.Web.Caching;
using System.Collections.Specialized;
using System.Collections.Generic;

namespace Marbles.Core.Authentication.OpenID
{
    /// <summary>
    /// Provides Authentication via the OpenID Framework.
    /// </summary>
    public class OpenIdProvider : AuthenticationProvider
    {
        public override string Provider { get { return "OpenId"; } }
        public override bool RequiresPassword { get { return false; } }

        private readonly Regex REGEX_LINK = new Regex(@"<link[^>]*/?>", RegexOptions.IgnoreCase | RegexOptions.Compiled);
        private readonly Regex REGEX_HREF = new Regex("href\\s*=\\s*(?:\"(?<1>[^\"]*)\"|(?<1>\\S+))", RegexOptions.IgnoreCase | RegexOptions.Compiled);

        /// <summary>
        /// Returns true if current request contains a valid OpenID token
        /// </summary>
        public bool IsOpenIdRequest
        {
            get
            {
                if (!HttpContext.Current.Request.HttpMethod.Equals("GET", StringComparison.OrdinalIgnoreCase)) { return false; }
                return (HttpContext.Current.Request.QueryString["openid.mode"] != null);
            }
        }

        /// <summary>
        /// Authenticates a given user on the selected Authentication Provider
        /// </summary>
        /// <param name="identity">Username to authenticate</param>
        /// <param name="password">Password</param>
        /// <returns>True if Successful.</returns>
        public override bool Login(string identity, string password)
        {
            return Login(identity, "email,fullname", "country,language");
        }

        /// <summary>
        /// Authenticates a given user on the selected Authentication Provider
        /// </summary>
        /// <param name="identity">Username to authenticate</param>
        /// <param name="requiredParameters">Required parameters for Authentication</param>
        /// <param name="optionalParameters">Optional parameters for Authentication</param>
        /// <returns>True if Successful.</returns>
        public bool Login(string identity, string requiredParameters, string optionalParameters)
        {
            try
            {
                StringDictionary dic = GetIdentityServer(identity);
                string server = dic["openid.server"];
                string delgate = dic["openid.delegate"] ?? identity;

                if (!string.IsNullOrEmpty(server))
                {
                    string redirectUrl = CreateRedirectUrl(requiredParameters, optionalParameters, delgate, identity, true);

                    OpenIdToken token = new OpenIdToken(identity);
                    SetCurrentToken(token);

                    HttpContext.Current.Response.Redirect(server + redirectUrl, true);
                    return true;
                }
            }
            catch
            {
            }

            return false;
        }

        /// <summary>
        /// Retrieves the User token for the Authenticated User
        /// </summary>
        /// <param name="identity">Username of account</param>
        /// <param name="password">Password of account</param>
        /// <returns>IToken Containing User Information</returns>
        public override IToken GetToken(string username, string password)
        {
            return Authenticate();
        }

        /// <summary>
        /// Authenticates the current user.
        /// </summary>
        /// <returns>Returns a valid token if Successful, else returns null</returns>
        public override IToken Authenticate()
        {            
            OpenIdToken token = GetCurrentToken() as OpenIdToken;

            if (token == null) { return new OpenIdToken(string.Empty); }

            NameValueCollection query = HttpContext.Current.Request.QueryString;

            if (query["openid.claimed_id"] != token.Identity) { return token; }

            token.IsSuccess = (query["openid.mode"] == "id_res");

            foreach (string name in query.Keys)
            {
                if (name.StartsWith("openid.sreg."))
                    token.Properties.Add(name.Replace("openid.sreg.", string.Empty), query[name]);
            }
            
            return token;
        }

        /// <summary>
        /// Returns the OpenID Identity Server
        /// </summary>
        /// <param name="identity">Retrieves the Identity Server for the current OpenID</param>
        /// <returns></returns>
        public StringDictionary GetIdentityServer(string identity)
        {
            using (WebClient client = new WebClient())
            {
                string html = client.DownloadString(identity);
                StringDictionary col = new StringDictionary();
                foreach (Match match in REGEX_LINK.Matches(html))
                {
                    AssignValue(match, col, "openid.server");
                    AssignValue(match, col, "openid.delegate");
                }

                return col;
            }
        }

        private void AssignValue(Match linkMatch, StringDictionary col, string name)
        {
            if (linkMatch.Value.IndexOf(name) > 0)
            {
                Match hrefMatch = REGEX_HREF.Match(linkMatch.Value);
                if (hrefMatch.Success)
                {
                    if (!col.ContainsKey(name))
                        col.Add(name, hrefMatch.Groups[1].Value);
                }
            }
        }

        private string CreateRedirectUrl(string requiredParameters, string optionalParameters, string delegateidentity, string identity,  bool returntoreferrer)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("?openid.ns=" + HttpUtility.UrlEncode("http://specs.openid.net/auth/2.0"));
            sb.Append("&openid.mode=checkid_setup");
            sb.Append("&openid.identity=" + HttpUtility.UrlEncode(delegateidentity));
            sb.Append("&openid.claimed_id=" + HttpUtility.UrlEncode(identity));

            if ((returntoreferrer) && (HttpContext.Current.Request.UrlReferrer!=null))
            {
                sb.Append("&openid.return_to=" + HttpUtility.UrlEncode(HttpContext.Current.Request.UrlReferrer.ToString()));
            }
            else
            {
                sb.Append("&openid.return_to=" + HttpUtility.UrlEncode(HttpContext.Current.Request.Url.ToString()));
            }
            if (!string.IsNullOrEmpty(requiredParameters) || !string.IsNullOrEmpty(optionalParameters))
            {
                sb.Append("&openid.ns.sreg=" + HttpUtility.UrlEncode("http://openid.net/extensions/sreg/1.1"));

                if (!string.IsNullOrEmpty(requiredParameters))
                    sb.Append("&openid.sreg.required=" + HttpUtility.UrlEncode(requiredParameters));

                if (!string.IsNullOrEmpty(optionalParameters))
                    sb.Append("&openid.sreg.optional=" + HttpUtility.UrlEncode(optionalParameters));
            }

            return sb.ToString();
        }

    }
}